/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.web.common.io;

import com.inspur.edp.web.common.customexception.WebCustomException;
import com.inspur.edp.web.common.logger.WebLogger;
import com.inspur.edp.web.common.utility.OperatingSystemUtility;
import com.inspur.edp.web.common.utility.StringUtility;
import io.iec.edp.caf.common.environment.EnvironmentUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 平台无关的文件类
 */
public class FileUtility {
    private static final String ENCODING = "UTF-8";
    private static final String UTF8_BOM_HEADER = "\ufeff";

    /**
     * 平台无关的路径分隔符
     */
    public static final String DIRECTORY_SEPARATOR_CHAR = File.separator;

    private static final Object _writeFileLock = new Object();

    private FileUtility() {

    }


    /**
     * Combine Path
     *
     * @param path1 the path 1
     * @param path2 the path 2
     * @return the string
     * @description 合并路径
     */
    public static String combine(String path1, String path2) {
        return Paths.get(path1).resolve(path2).toString();
    }

    /**
     * Combine Path
     *
     * @param path1 the path 1
     * @param path2 the path 2
     * @param path3 the path 3
     * @return the string
     * @description 合并路径
     */
    public static String combine(String path1, String path2, String path3) {
        return combine(combine(path1, path2), path3);
    }

    /**
     * Combine Path
     *
     * @param path1 the path 1
     * @param path2 the path 2
     * @param path3 the path 3
     * @param path4 the path 4
     * @return the string
     * @description 合并路径
     */
    public static String combine(String path1, String path2, String path3, String path4) {
        return combine(combine(path1, path2), combine(path3, path4));
    }

    /**
     * 文件目录合并
     * 请注意：除了path1，more中不允许出现绝对路径
     *
     * @param path1
     * @param more
     * @return
     */
    public static String combineOptional(String path1, String... more) {
        return Paths.get(path1, more).normalize().toString();
    }

    /**
     * Check file or directory exists
     *
     * @return <code>true</code> if and only if the file or directory denoted
     * by this abstract pathname exists; <code>false</code> otherwise
     * @description 检查文件或目录是否存在
     */
    public static boolean exists(String path) {
        return exists(new File(path));
    }

    /**
     * 检测文件或目录是否存在
     *
     * @param fileHandler 文件或目录句柄
     * @return
     */
    private static boolean exists(File fileHandler) {
        return fileHandler.exists();
    }

    /**
     * 判断一个文件夹是否是空
     *
     * @param path
     * @return
     */
    public static boolean isEmptyFolder(String path) {
        File file = new File(path);
        if (file.isDirectory()) {
            return file.list().length == 0;
        }
        return false;
    }


    /**
     * Read file content as string
     *
     * @param path the full file path
     * @return the string
     * @description 根据文件的完整路径，读取文件内容（UTF-8编码）
     */
    public static String readAsString(String path) {
        File file = new File(path);
        long fileLength = file.length();
        byte[] fileContent = new byte[(int) fileLength];
        try {
            FileInputStream in = new FileInputStream(file);
            in.read(fileContent);
            in.close();
        } catch (IOException e) {
            WebLogger.Instance.error(e);
            throw new RuntimeException("readAsString failed", e);
        }

        try {
            // 判断有没有utf-8 bom头， 有则去除。
            String fileContents = new String(fileContent, ENCODING);
            if (fileContents.startsWith(UTF8_BOM_HEADER)) {
                fileContents = fileContents.substring(1);
            }
            return fileContents;
        } catch (UnsupportedEncodingException e) {
            WebLogger.Instance.errorWithCustomMessage(e, "The OS does not support " + ENCODING);
            return null;
        }
    }

    /**
     * Delete a file.
     *
     * @description 删除文件
     */
    public static boolean deleteFile(String filePath) {
        File file = new File(filePath);
        if (!exists(file) || !file.isFile()) {
            return false;
        }

        return file.delete();
    }

    /**
     * 设置目前权限
     * 当前设置的可读、可写、可执行权限，即linux的  777
     *
     * @param filePath
     */
    public static void setPermission(String filePath) {
        if (StringUtility.isNullOrEmpty(filePath)) {
            return;
        }
        File file = new File(filePath.trim());
        if (file.exists()) {
            try {
                file.setReadable(true, false);
                file.setWritable(true, false);
                file.setExecutable(true, false);
            } catch (Exception ex) {

            }
        }
    }

    /**
     * Delete a directory.
     */
    public static boolean deleteDirectory(String directoryPath) {
        File directory = new File(directoryPath);
        if (!exists(directory) || !directory.isDirectory()) {
            return false;
        }

        return directory.delete();
    }

    /**
     * 使用fileutils方法的强制删除方法 强制删除文件目录
     *
     * @param directoryPath
     */
    public static void forceDelete(String directoryPath) {
        if ((new File(directoryPath)).isDirectory() && (new File(directoryPath)).exists()) {
            try {
                FileUtils.forceDelete(new File(directoryPath));
            } catch (IOException e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }

    public static void deleteFolder(String directoryPath) {
        File file = new File(directoryPath);
        deleteFolder(file);
    }


    /**
     * 删除文件夹及其子文件夹
     *
     * @param folder
     * @
     */
    public static void deleteFolder(File folder) {
        if (!folder.exists()) {
            return;
        }
        File[] files = folder.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    //递归直到目录下没有文件
                    deleteFolder(file);
                } else {
                    //删除
                    file.delete();
                }
            }
        }
        //删除
        folder.delete();

    }


    /**
     * 将文本写入指定文件; 如果文件不存在先创建文件，再执行写入操作; 如果文件存在，删除后重新创建并写入。
     *
     * @param path     目标文件路径
     * @param fileName 目标文件名称
     * @param contents 写入文件的内容
     */
    public static void writeFile(String path, String fileName, String contents) {
        String filePath = combine(path, fileName);

        writeFile(filePath, contents);
    }

    /**
     * 将文本写入指定文件; 如果文件不存在先创建文件，再执行写入操作; 如果文件存在，删除后重新创建并写入。
     *
     * @param fileNameAndPath 目标文件路径
     * @param contents        写入文件的内容
     */
    public static void writeFile(String fileNameAndPath, String contents) {
        fileNameAndPath = getPlatformIndependentPath(fileNameAndPath);

        // 如果文件路径不存在 那么创建对应的文件目录
        File file = new File(fileNameAndPath);

        File fileParent = file.getParentFile();
        synchronized (_writeFileLock) {
            if (!fileParent.exists()) {
                fileParent.mkdirs();
            }

            if (exists(file)) {
                clear(file);
            } else {
                createFile(file);
            }
            // 如果写入的内容为null  那么转换成空字符串写入
            if (StringUtility.isNullOrEmpty(contents)) {
                contents = "";
            }
            updateFile(fileNameAndPath, contents);
        }
    }

    public static void reName(String source, String dest) {
        if (!StringUtility.isNullOrEmpty(source) && !StringUtility.isNullOrEmpty(dest)) {
            File sourceFile = new File(source);
            if (sourceFile.exists()) {
                // 重命名文件目录
                File destFile = new File(dest);
                if (destFile.exists()) {
                    destFile.delete();
                }
                sourceFile.renameTo(destFile);
            }
        }
    }


    /**
     * 获取当前运行目录
     * 服务器目录
     *
     * @return
     */
    public static String getCurrentWorkPath(boolean isUpgradeTool) {
        // 如果运行在tool中 那么运行目录需要进行调整
        if (isUpgradeTool) {
            String caf_boot_home = System.getenv("CAF_BOOT_HOME");

            String targetPath = null;
            if (caf_boot_home == null) {
                targetPath = toAbsolutePath("../../../../");
            } else {
                targetPath = toAbsolutePath(Paths.get(caf_boot_home).resolve("..").toString());
            }

            return targetPath;
        }

        return EnvironmentUtil.getBasePath();
    }

    /**
     * 获取安装盘server path
     *
     * @return
     */
    public static String getServerRTPath() {
        return EnvironmentUtil.getServerRTPath();
    }


    /**
     * 将路径转换成为绝对路径
     *
     * @param maybeRelative
     * @return
     */
    private static String toAbsolutePath(String maybeRelative) {
        try {
            Path path = Paths.get(maybeRelative);
            Path effectivePath = path;
            if (!path.isAbsolute()) {
                Path base = Paths.get("");
                effectivePath = base.resolve(path).toAbsolutePath();
            }
            return effectivePath.normalize().toString();
        } catch (Exception e) {
            return maybeRelative;
        }
    }

    /**
     * 移动审批调用 不能删除
     *
     * @return
     */
    public static String getCurrentWorkPath() {

        return EnvironmentUtil.getBasePath();
        //return System.getProperty("user.dir");
    }

    /**
     * 清空文件中内容
     *
     * @param file 待清空文件句柄
     */
    private static void clear(File file) {
        try {
            FileWriter fileWriter = new FileWriter(file);
            fileWriter.write("");
            fileWriter.flush();
            fileWriter.close();
        } catch (IOException e) {
            WebLogger.Instance.error(e);
        }
    }

    /**
     * 更新文件
     *
     * @param fullPath
     * @param content
     */
    public static void updateFile(String fullPath, String content) {
        try {
            byte[] buffer = content.getBytes(StandardCharsets.UTF_8);
            FileOutputStream fos = new FileOutputStream(fullPath, true);
            fos.write(buffer);
            fos.close();
        } catch (IOException e) {
            WebLogger.Instance.error(e);
        }
    }

    /**
     * 创建文件
     *
     * @param path 待创建文件路径
     */
    public static void createFile(String path) {
        try {
            path = getPlatformIndependentPath(path);
            File file = new File(path);
            if (!exists(file)) {
                file.createNewFile();
            }
        } catch (Exception ex) {
            WebLogger.Instance.error(ex);
        }
    }

    /**
     * 创建文件
     *
     * @param file 待创建文件句柄
     */
    private static boolean createFile(File file) {
        try {
            file.createNewFile();
            return true;
        } catch (Exception ex) {
            WebLogger.Instance.error(ex);
            return false;
        }
    }

    /**
     * 递归创建文件目录
     *
     * @param path
     */
    public static void createDirectory(String path) {
        if (StringUtility.isNullOrEmpty(path)) {
            return;
        }

        try {
            path = getPlatformIndependentPath(path);
            File file = new File(path);
            // 目录不存在时触发
            if (!exists(file)) {
                file.mkdirs();
            }
        } catch (Exception ex) {
            WebLogger.Instance.error(ex);
        }
    }

    /**
     * 判断路径是否是绝对路径
     *
     * @param dirPath
     * @return
     */
    public static boolean isAbsolute(String dirPath) {
        File file = new File(dirPath);
        return file.isAbsolute();
    }

    /**
     * 获取对应的文件名称
     * 如果传递的路径末尾不是文件名称，那么使用最末尾的字符内容
     *
     * @param file
     * @return
     */
    public static String getFileName(File file) {
        return file.getName();
    }

    /**
     * 获取对应文件路径的文件名称参数
     *
     * @param filePath
     * @return
     */
    public static String getFileName(String filePath) {
        return getFileName(new File(filePath));
    }


    /**
     * 路径转换，获取平台无关的路径
     *
     * @param path 待转换路径
     */
    public static String getPlatformIndependentPath(String path) {

        if (path == null) {
            return null;
        }
        // 如果文件路径为空字符串 那么不进行任何的转换处理
        if (path.isEmpty()) {
            return path;
        }


        return path.replace("\\\\", "/").replace("\\", "/");
    }

    /**
     * 文件夹拷贝，遇到同名文件覆盖
     *
     * @param sourceFolderFullPath 源文件夹路径
     * @param destFolderFullPath   目标文件夹路径
     *                             |
     */
    public static void copyFolder(String sourceFolderFullPath, String destFolderFullPath) {
        copyFolder(sourceFolderFullPath, destFolderFullPath, true);
    }

    /**
     * 文件夹拷贝
     *
     * @param sourceFolderFullPath 源文件夹路径
     * @param destFolderFullPath   目标文件夹路径
     * @param isOverWrite          是否覆盖同名文件
     */
    public static void copyFolder(String sourceFolderFullPath, String destFolderFullPath, boolean isOverWrite) {
        if (sourceFolderFullPath == null || sourceFolderFullPath.isEmpty() || destFolderFullPath == null || destFolderFullPath.isEmpty()) {
            return;
        }

        sourceFolderFullPath = getPlatformIndependentPath(sourceFolderFullPath);
        destFolderFullPath = getPlatformIndependentPath(destFolderFullPath);

        if (!exists(destFolderFullPath)) {
            createDirectory(destFolderFullPath);
        }

        ArrayList<File> files = getFiles(sourceFolderFullPath, false);
        for (File file : files) {
            String fileName = file.getName();
            String dest = combine(destFolderFullPath, fileName);
            copyFile(file.getAbsolutePath(), dest, isOverWrite);
        }

        ArrayList<File> folders = getDirectories(sourceFolderFullPath);
        for (File folder : folders) {
            String name = folder.getName();
            String dest = combine(destFolderFullPath, name);
            // 递归拷贝
            copyFolder(folder.getAbsolutePath(), dest);
        }
    }

    /**
     * 获取某一文件夹下的所有文件
     **/
    public static ArrayList<File> getFiles(String path, boolean rescure) {
        ArrayList<File> filesInFolder = new ArrayList<>();
        if (rescure) {
            rescureGetFiles(path, filesInFolder);
        } else {
            File file = new File(path);
            // 如果这个路径是文件夹
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    if (f.isFile()) {
                        filesInFolder.add(f);
                    }
                }
            }
        }

        return filesInFolder;
    }

    /**
     * 递归获取文件目录下的所有文件列表
     *
     * @param path
     * @param fileList
     */
    private static void rescureGetFiles(String path, List<File> fileList) {
        File file = new File(path);
        File[] files = file.listFiles();
        if (files != null) {
            for (File fileItem : files) {
                // 如果为文件目录
                if (fileItem.isDirectory()) {
                    rescureGetFiles(fileItem.getPath(), fileList);
                } else {
                    fileList.add(fileItem);
                }
            }
        }
    }

    /**
     * 获取某一文件夹下的所有文件夹
     */
    public static ArrayList<File> getDirectories(String path) {
        // 初始化制定路径
        File file = new File(path);
        ArrayList<File> fileList = new ArrayList<>();
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            for (File value : files) {
                if (value.isDirectory()) {
                    fileList.add(value);
                }
            }
        }
        return fileList;
    }

    /**
     * 拷贝文件：支持覆盖已存在文件
     **/
    public static void copyFile(String sourcePath, String destinationPath, boolean isOverWrite) {
        if (sourcePath != null && destinationPath != null) {
            fileCopy(new File(sourcePath), new File(destinationPath));
        }
    }


    private static void fileCopy(File file1, File file2) {
        try {
            FileUtils.copyFile(file1, file2);

        } catch (Exception e) {
            WebLogger.Instance.error(e);
            throw new WebCustomException(e.getMessage() + e.getStackTrace());
        }
    }

    /**
     * 判断两个文件的内容是否相同，文件名要用绝对路径
     *
     * @param fileName1 ：文件1的绝对路径
     * @param fileName2 ：文件2的绝对路径
     * @return 相同返回true，不相同返回false
     */
    public static boolean isSameFile(String fileName1, String fileName2) {
        FileInputStream fis1 = null;
        FileInputStream fis2 = null;
        try {
            fis1 = new FileInputStream(fileName1);
            fis2 = new FileInputStream(fileName2);

            //返回总的字节数
            int len1 = fis1.available();
            int len2 = fis2.available();

            //长度相同，则比较具体内容
            if (len1 == len2) {
                //建立两个字节缓冲区
                byte[] data1 = new byte[len1];
                byte[] data2 = new byte[len2];

                //分别将两个文件的内容读入缓冲区
                fis1.read(data1);
                fis2.read(data2);

                //依次比较文件中的每一个字节
                for (int i = 0; i < len1; i++) {
                    //只要有一个字节不同，两个文件就不一样
                    if (data1[i] != data2[i]) {
                        return false;
                    }
                }
                return true;
            } else {
                //长度不一样，文件肯定不同
                return false;
            }
        } catch (IOException e) {
            WebLogger.Instance.error(e);
        } finally {//关闭文件流，防止内存泄漏
            if (fis1 != null) {
                try {
                    fis1.close();
                } catch (IOException e) {
                    //忽略
                    WebLogger.Instance.error(e);
                }
            }
            if (fis2 != null) {
                try {
                    fis2.close();
                } catch (IOException e) {
                    //忽略
                    WebLogger.Instance.error(e);
                }
            }
        }
        return false;
    }


    public static String getFileNameWithPath(String filePath) {
        String _filePath = filePath.trim();
        return _filePath.substring(_filePath.lastIndexOf("/") + 1);
    }


    /**
     * 求取两个路径得相对路径
     *
     * @param path1
     * @param path2
     * @param replaceSeparator 是否替换路径分隔符
     * @return
     */
    public static String getRelativePath(String path1, String path2, boolean replaceSeparator) {
        if (StringUtility.isNullOrEmpty(path1) || StringUtility.isNullOrEmpty(path2)) {
            return "";
        }

        if (replaceSeparator) {
            path1 = getPlatformIndependentPath(path1);
            path2 = getPlatformIndependentPath(path2);
        }

        String strRelativePath = Paths.get(path1).relativize(Paths.get(path2)).toString();
        // 转换成路径无关的文件路径
        if (replaceSeparator) {
            strRelativePath = getPlatformIndependentPath(strRelativePath);
        }
        return strRelativePath;
    }

    /**
     * 获取绝对路径的头
     * windows下为 "盘符 + :"
     * nix下为 "\"
     *
     * @param currentPath
     * @return
     */
    public static String getAbsolutePathHead(String currentPath) {
        String absolutePathHead = "";
        if (StringUtils.isEmpty(currentPath)) {
            return absolutePathHead;
        }
        currentPath = currentPath.replace("/", DIRECTORY_SEPARATOR_CHAR).replace("\\", DIRECTORY_SEPARATOR_CHAR);

        if (OperatingSystemUtility.isWindows()) {
            //TODO N转J 此处N版获取路径中盘符是通过以下代码实现的，在java中无对等方法，因此采用获取绝对路径:前的第一个字符解决，可能存在bug
            String absolutePath = new File(currentPath).getAbsolutePath();
            int index = absolutePath.indexOf(':');
            if (index <= 0) {
                throw new RuntimeException("Get Directory Root Failed When doing the GetPathVolume function");
            }
            absolutePathHead = absolutePath.substring(index - 1, 2);
        } else {
            absolutePathHead = DIRECTORY_SEPARATOR_CHAR;
        }

        return absolutePathHead;
    }

    /**
     * 获取执行的临时目录
     *
     * @return
     */
    public static String getTmpDir() {
        return System.getProperty("java.io.tmpdir");
    }


    /**
     * Fail for below cases
     * <p>
     * "/path/../makefile",
     * "/path/dir.test/makefile"
     */
    public static String getFileExtension(String fileName) {
        if (fileName == null) {
            throw new IllegalArgumentException("fileName must not be null!");
        }

        String extension = "";

        int index = fileName.lastIndexOf('.');
        if (index > 0) {
            extension = fileName.substring(index);
        }

        return extension;

    }
}
